/*--------------------------------------------------------------
 * File: Driver_flash.c 编码类型：C语言  UTF-8
 * Author WRS (1352509846@qq.com)
 * 功能: W25Qxx驱动程序
 * 共4MB空间，每页256字节
 * 16页是一个扇区（256 * 16 = 4096）4kb
 * 4kb为一个扇区（sector）
 * 16个扇区为一个块（block）
 * Version 0.1
 * Date 2021-10-07 
 * 
 * @copyright Copyright (c) 2021
 * 
----------------------------------------------------------------*/

#include "Driver_flash.h"

/*--------------------------------------------------------------
 * 功能: 读取W25Qxx的内部寄存器\数据
 * 本质是对SPI读写函数的二次封装
----------------------------------------------------------------*/
static u8 W25Qxx_Read(void)
{
    return spix_read_write_byte(SPI2,0xFF);
}

/*--------------------------------------------------------------
 * 功能: 写入W25Qxx的内部寄存器\数据
 * 本质是对SPI读写函数的二次封装
----------------------------------------------------------------*/
static void W25Qxx_Write(u8 data)
{
    spix_read_write_byte(SPI2,data);
}

/*--------------------------------------------------------------
 * 功能: 读取W25Qxx的芯片ID
 * 用于检测是否挂载成功
----------------------------------------------------------------*/
static u32 W25Qxx_Read_ID(void)
{
    u32 W25QxxID = 0;
    u8 temp[3] = {0};

    W25QXX_CS_PORT = START;     //启动发送
    W25Qxx_Write(W25Qxx_DeviceID);
    temp[0] = W25Qxx_Read();  
    temp[1] = W25Qxx_Read();
    temp[2] = W25Qxx_Read();
    W25QXX_CS_PORT = STOP;      //停止发送

	W25QxxID = (temp[0]<<16)|(temp[1]<<8)|temp[2];
    return W25QxxID;
}

/*--------------------------------------------------------------
 * 功能: 初始化W25QXX
 * 需要根据不同型号的芯片设置不同的ID号
 * 返回值: u8 成功返回SUCCESS  失败返回ERROR
----------------------------------------------------------------*/
u8 W25Qxx_init(void)
{
    spi2_init();

    //初始化W25Qxx的片选引脚
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;                            //片选引脚CS
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;                         //推挽输出
    GPIO_Init(GPIOB,&GPIO_InitStructure);                               //初始胡GPIO配置
    GPIO_SetBits(GPIOB,GPIO_Pin_12);                                    //拉高CS引脚，防止误传数据
    
    //读取W25Qxx的ID号
    if(W25Q32ID == W25Qxx_Read_ID())
    {
        INFO("W25Q32 init SUCCEED");
        return SUCCESS;
    }
    else
    {
        MY_ERROR("W25Q32 init Fail");
        return ERROR;
    }
}

/*--------------------------------------------------------------
 * 功能: W25Qxx芯片写使能
----------------------------------------------------------------*/
static void W25Qxx_Write_Enable(void)
{
    W25QXX_CS_PORT = START;
    W25Qxx_Write(W25Qxx_WriteEnable);
    W25QXX_CS_PORT = STOP;
}

/*--------------------------------------------------------------
 * 功能: W25Qxx芯片写禁止
----------------------------------------------------------------*/
static void W25Qxx_Write_Disable(void)
{
    W25QXX_CS_PORT = START;
    W25Qxx_Write(W25Qxx_WriteDisable);
    W25QXX_CS_PORT = STOP;
}

/*--------------------------------------------------------------
 * 功能: 获取W25QXX的状态,等待写入或擦除结束
 * 循环1秒检测写入或擦除是否成功，需要根据不同芯片特性修改超时时间
----------------------------------------------------------------*/
static void W25Qxx_WaitForWriteEnd(void)
{
    u8 w25qxx_status = 0;
    u16 SPItimeout = TIMEOUT;

    W25QXX_CS_PORT = START;
    
    W25Qxx_Write(W25Qxx_ReadStatusReg);
    do{
        w25qxx_status = W25Qxx_Read();
        if((SPItimeout--) == 0)
        {
            MY_ERROR("W25QXX  write/ERASE fail ");
            return ;
        }
        delay_ms(1);
    } while ((w25qxx_status&0x01) == SET);  //正在写入
    
    W25QXX_CS_PORT = STOP;
}

/*--------------------------------------------------------------
 * 功能: W25QXX数据扇区擦除（每个扇区4KB，扇区是最小擦除单位）
 * SectorAddr：扇区首地址
----------------------------------------------------------------*/
void W25Qxx_Sector_Erase(u32 SectorAddr)
{
    W25Qxx_Write_Enable();       //发送W25QXX写使能
    W25Qxx_WaitForWriteEnd();    //检测芯片状态并等待结束

    W25QXX_CS_PORT = START;

    W25Qxx_Write(W25Qxx_SectorErase);               //发送扇区擦除指令
    // W25Qxx_Write(SectorAddr & 0xFF000000>>24);   //发送要擦除的扇区地址的高8位(大于128MB才有高8位地址)
    W25Qxx_Write((SectorAddr & 0xFF0000)>>16);      //发送要擦除的扇区地址的中高8位
    W25Qxx_Write((SectorAddr & 0xFF00)>>8);         //发送要擦除的扇区地址的低高8位
    W25Qxx_Write(SectorAddr & 0xFF);                //发送要擦除的扇区地址的低高8位

    W25QXX_CS_PORT = STOP;
    W25Qxx_WaitForWriteEnd();    //检测芯片状态并等待结束
}

/*--------------------------------------------------------------
 * 功能: W25QXX整个芯片擦除（等待时间较长）
----------------------------------------------------------------*/
void W25Qxx_Chip_Erase(void)
{
    u8 w25qxx_status = 0;
    u16 SPItimeout = TIMEOUT;

    W25Qxx_Write_Enable();                  // 发送W25QXX写使能 
    W25QXX_CS_PORT = START;
    W25Qxx_Write(W25Qxx_ChipErase);         // 发送芯片擦除指令
    W25QXX_CS_PORT = STOP;
    
    //等待芯片擦除结束（需要根据芯片容量修改延时时间）
    W25QXX_CS_PORT = START;
    W25Qxx_Write(W25Qxx_ReadStatusReg);
    do{
        w25qxx_status = W25Qxx_Read();
        if((SPItimeout--) == 0)
        {
            MY_ERROR("W25QXX  Chip ERASE fail ");
            return ;
        }
        delay_ms(20);
    } while ((w25qxx_status&0x01) == SET);  //正在写入
    W25QXX_CS_PORT = STOP;  
}

/*--------------------------------------------------------------
 * 功能: W25Qxx页写入
 * 参数: str 要写入的内容
 * 参数: WriteAddr 写入地址
 * 参数: NumByteToWrite 写入长度
----------------------------------------------------------------*/
void W25Qxx_Page_Write(u8 *str, u32 WriteAddr, u16 NumByteToWrite)
{
    if(str == NULL)
    {
        MY_ERROR("str is NULL");
        return ;
    }

    W25Qxx_Write_Enable();       //发送W25QXX写使能
    W25QXX_CS_PORT = START;

    W25Qxx_Write(W25Qxx_PageProgram);               //发送页写入指令
    // W25Qxx_Write(WriteAddr & 0xFF000000>>24);    //发送要擦除的扇区地址的高8位(大于128MB才有高8位地址)
    W25Qxx_Write((WriteAddr & 0xFF0000)>>16);       //发送要擦除的扇区地址的中高8位
    W25Qxx_Write((WriteAddr & 0xFF00)>>8);          //发送要擦除的扇区地址的低高8位
    W25Qxx_Write(WriteAddr & 0xFF);                 //发送要擦除的扇区地址的低高8位

    if(NumByteToWrite>256)
    {
        NumByteToWrite = 256;
        MY_ERROR("The writing is too long, exceeding the scope of each page");
        return ;
    }

    while(NumByteToWrite--)
    {
        W25Qxx_Write(*str);
        str++;
    }

    W25QXX_CS_PORT = STOP;
    
    W25Qxx_WaitForWriteEnd();//检测芯片状态并等待结束
}

/*--------------------------------------------------------------
 * 功能: W25Qxx数据写入（不定长数据）
 * 参数: str 要写入的数据
 * 参数: WriteAddr 写入的地址
 * 参数: NumByteToWrite 写入的长度
----------------------------------------------------------------*/
void W25Qxx_Data_Write(u8* str, u32 WriteAddr, u16 NumByteToWrite)
{
    if(str == NULL)
    {
        MY_ERROR("str is NULL");
        return ;
    }

    u8 NumOfPage = 0;
    u8 NumOfSingle = 0;
    u8 Addr = 0;
    u8 count = 0;

    //求余运算，若WriteAddr是256字节的整数倍，则写入整数页
    Addr = WriteAddr % 256;
    count = 256 - Addr;

    //计算出要写入的整数页数
    NumOfPage = NumByteToWrite / 256;
    NumOfSingle = NumByteToWrite % 256;

    if(Addr == 0)   //写入地址刚好页对齐
    {
        if(NumOfPage == 0)//写入的数据小于每页的256字节大小
        {
            W25Qxx_Page_Write(str,WriteAddr,NumByteToWrite);
        }
        else
        {
            while(NumOfPage--)//先把整数页写完
            {
                W25Qxx_Page_Write(str,WriteAddr,256);
                WriteAddr += 256;
                str += 256;
            }
            W25Qxx_Page_Write(str,WriteAddr,NumOfSingle);//写入剩下的不满256字节的数据
        }
    }
    else    //写入的地址没有页对齐
    {
        NumByteToWrite -= count;            //整页数据总和
        NumOfPage = NumByteToWrite / 256;   //整数页
        NumOfSingle = NumByteToWrite % 256; //剩下的不足一页的数据长度

        W25Qxx_Page_Write(str,WriteAddr,count);
        WriteAddr += count;
        str += count;

        while (NumOfPage--) 
        {
            W25Qxx_Page_Write(str,WriteAddr,256);//写整数页
            WriteAddr += 256;
            str += 256;
        }
        if(NumOfSingle != 0)
        {
            W25Qxx_Page_Write(str,WriteAddr,NumOfSingle);//写入剩下的（不足一页）的数据
        }
    }
}

/*--------------------------------------------------------------
 * 功能: 读取W25Qxx中的数据
 * 参数: str 读出数据要存放的位置
 * 参数: ReadAddr 读取地址
 * 参数: NumByteToRead 读取长度
----------------------------------------------------------------*/
void W25Qxx_Data_Read(u8* str,u32 ReadAddr,u32 NumByteToRead)
{
    W25QXX_CS_PORT = START;

    W25Qxx_Write(W25Qxx_ReadData);              //发送读取数据指令
    // W25Qxx_Write(ReadAddr & 0xFF000000>>24); //发送要擦除的扇区地址的高8位
    W25Qxx_Write((ReadAddr & 0xFF0000)>>16);    //发送要擦除的扇区地址的中高8位
    W25Qxx_Write((ReadAddr & 0xFF00)>>8);       //发送要擦除的扇区地址的低高8位
    W25Qxx_Write(ReadAddr & 0xFF);              //发送要擦除的扇区地址的低高8位

    while(NumByteToRead--)
    {
        *str = W25Qxx_Read();
        str++;
    }

    W25QXX_CS_PORT = STOP;
}

/*--------------------------------------------------------------
 * 功能: 进入掉电模式
----------------------------------------------------------------*/
void W25Qxx_Powerdown(void)
{
    W25QXX_CS_PORT = START;
    W25Qxx_Write(W25Qxx_PowerDown); //发送掉电命令
    W25QXX_CS_PORT = STOP;
}

/*--------------------------------------------------------------
 * 功能: 唤醒
----------------------------------------------------------------*/
void W25Qxx_WakeUP(void)
{
    W25QXX_CS_PORT = START;
    W25Qxx_Write(W25Qxx_ReleasePowerdown); //发送唤醒命令
    W25QXX_CS_PORT = STOP;
}
